import { utils, internal } from './api';

var error = internal.error;

function Handler(type, target, callback, listener, priority) {
    this.type = type;
    this.callback = callback;
    this.listener = listener || null;
    this.priority = priority || 0;
    this.target = target;
}

Handler.prototype.trigger = function(evt) {
    if (!evt) {
        evt = new EventData(this.type);
        evt.target = this.target;
    } else if (evt.target != this.target || evt.type != this.type) {
        error("[Handler] event target/type have changed.");
    }
    this.callback.call(this.listener, evt);
};

function EventData(type, target, data) {
    this.type = type;
    this.target = target;
    if (data) {
        utils.defaults(this, data);
        this.data = data;
    }
}

EventData.prototype.stopPropagation = function() {
    this.__stop__ = true;
};

//  Base class for objects that dispatch events
export function EventDispatcher() {}


// @obj (optional) data object, gets mixed into event
// @listener (optional) dispatch event only to this object
EventDispatcher.prototype.dispatchEvent = function(type, obj, listener) {
    var evt;
    // TODO: check for bugs if handlers are removed elsewhere while firing
    var handlers = this._handlers;
    if (handlers) {
        for (var i = 0, len = handlers.length; i < len; i++) {
            var handler = handlers[i];
            if (handler.type == type && (!listener || listener == handler.listener)) {
                if (!evt) {
                    evt = new EventData(type, this, obj);
                }
                else if (evt.__stop__) {
                    break;
                }
                handler.trigger(evt);
            }
        }
    }
};

EventDispatcher.prototype.addEventListener =
    EventDispatcher.prototype.on = function(type, callback, context, priority) {
        context = context || this;
        priority = priority || 0;
        var handler = new Handler(type, this, callback, context, priority);
        // Insert the new event in the array of handlers according to its priority.
        var handlers = this._handlers || (this._handlers = []);
        var i = handlers.length;
        while (--i >= 0 && handlers[i].priority < handler.priority) {}
        handlers.splice(i+1, 0, handler);
        return this;
    };

// Remove an event handler.
// @param {string} type Event type to match.
// @param {function(BoundEvent)} callback Event handler function to match.
// @param {*=} context Execution context of the event handler to match.
// @return {number} Returns number of handlers removed (expect 0 or 1).
EventDispatcher.prototype.removeEventListener = function(type, callback, context) {
    context = context || this;
    var count = this.removeEventListeners(type, callback, context);
    return count;
};

// Remove event handlers; passing arguments can limit which listeners to remove
// Returns nmber of handlers removed.
EventDispatcher.prototype.removeEventListeners = function(type, callback, context) {
    var handlers = this._handlers;
    var newArr = [];
    var count = 0;
    for (var i = 0; handlers && i < handlers.length; i++) {
        var evt = handlers[i];
        if ((!type || type == evt.type) &&
            (!callback || callback == evt.callback) &&
            (!context || context == evt.listener)) {
            count += 1;
        }
        else {
            newArr.push(evt);
        }
    }
    this._handlers = newArr;
    return count;
};

EventDispatcher.prototype.countEventListeners = function(type) {
    var handlers = this._handlers,
        len = handlers && handlers.length || 0,
        count = 0;
    if (!type) return len;
    for (var i = 0; i < len; i++) {
        if (handlers[i].type === type) count++;
    }
    return count;
};

